<?php
/**
 * This class implements a connection to Twitter
 *
 * @author Thomas Kappel
 * @copyright Copyright (c) 2008
 * @version $Id: Default.php 2462 2008-07-10 21:11:00Z tkappel $
 */

function aboutTwitter() {
  return array(
    "name"    => "Twitter"
  );
}

class Twitter extends Network_Abstract implements findContactsOfContacts, Microblogging {
  public $_Name            = "Twitter";// string:          name of the network
  public $user_id          = 0;        // string:          user id within the network
  public $me               = false;    // array:           all information about the user(account) himself 
  public $contacts         = false;    // array of arrays: data of contacts 
  public $username         = false;
  protected $pass          = false;
  public $server_addr      = "http://twitter.com/";

  public $activation_link  = false;   // string: url of the login page (optional)
  
  protected static $_instance  = null;
  
  public function __construct($account = false) {
    $this->NetworkName="Twitter";
    $this->account = $account ? $account : false;
    self::$_instance = $this;
  }

  public function supports($method) {
    switch($method) {
      case "find_friends_of_friends"         : return true;
      case "multiple_accounts"               : return false; // one person should have only one account 
      case "require_requests_for_friendship" : return false;  // no need to send these messages
      case "save_friends_to_network"         : return true;
      case "search_members"                  : return false; // find account of a person you already know from other networks
      case "disconnect"                      : return false;
      default                                : return false;
    }
  }
  
  /*
   * @returns URL of the Social Network Service
   */  
  public function getServiceHomepage() {
    return "http://www.twitter.com";
  }

  /*
   * @returns associative array of arrays:
   *  "mandatory" => required fields for logging in (for example: "uri", "user", "pass")
   *  "optional"  => some more allowed fields
   */  
  public function getAccountingRequirements() {
    return array(
      "mandatory" => array("user"),
      "optional"  => array()
    );
  }

  public function require_connect_button() {
    return true;
  }

  public function search($contact) {
    // still does not work 
  }
  
  /*
   * establishes connection to the network
   * 
   * @param: URI, username, password (all optional, depending on the network)
   * @returns: information about the user or false on error
   */
  public function connect($URI="", $user="", $pass='') {
    $this->username = $user;
    $session = SocializrSession::getInstance();
    if($session->getAttribute('Twitter_pass') || strlen($pass)>0) {
      $session->setAttribute('Twitter_pass', $pass);
      $template = 'message';
      $message  = 'OK';
      $reload = true;
    } else {
      $template = 'enter_password';
      $message  = '********';
      $reload = false;
    }
    return array(
      "code"        => 200,
      "message"     => $message,
      "template"    => $template,
      //"action"      => $action,
      "reload"      => $reload,
      "user"        => $user
    );
  }
  
  private function getPassword() {
    $session = SocializrSession::getInstance();
    return $session->getAttribute('Twitter_pass');
  }

  /*
   * @returns true if connection is already established
   */
  public function connected() {
    return strlen($this->getPassword())>0 ? true : false;
  }

  /*
   * close connection
   * 
   * @returns: boolean (true on success)
   */
  public function disconnect() {}
  
  /*
   * @returns: array of arrays holding fetchable information of all contacts
   */
  public function fetchContacts() {
    $xml = $this->request('statuses/friends.xml?lite=true');
    $sxml = simplexml_load_string($xml);
    //xmlObject2Array
    $friends = $this->xmlObject2Array($sxml);
//    $friends = $this->convert_xml_to_array($sxml, $method, $params);
    
    /*
     * Twitter does not support many requests
     * but returns not only a contact list but the full information we need
     */ 
    $friend_list = array();
    if(is_array($friends['user'])) {
      foreach($friends['user'] as $friend) {
        $nick = $friend["screen_name"];
        $friend_list[] = $nick;
        $this->contacts[$nick] = $this->mapContactData($friend);
      }
      // keep result in mind
      $session = SocializrSession::getInstance();
      $session->setAttribute('twitter_contacts', $this->contacts);
    }
    return $friend_list;
  }
  
  /**
   * @param: id of a contact
   * @return: standardised associative array of information about the user
   *           - foaf attributes
   *           - one element with the key "full_data" contains all fetchable information
   * @return associative array or false on error
   */
  function fetchContact($nick) {
    // we already know everything we need
    if(!$this->contacts) {
      $session = SocializrSession::getInstance();
      $this->contacts = $session->getAttribue('twitter_contacts');
    }
    if(!$this->contacts) return false;
    return $this->contacts[$nick];
  }

  /**
   * @param: id of a contact
   * @return: array of user ids of contacts of the user
   * @return false on error 
   */ 
  public function fetchContactsOfContacts($uid) {
    $this->request('statuses/friends/'.$uid.'.xml?lite=true');
    return false;
  }

  /**
   * @param: contact information (as returned by fetchContact())
   * @return: unique identifier of this account within the network
   */
  public function getAccountIdentifier($contact) {
    return $contact["accountName"];
  }

  /**
   * @return object: instance of this class
   */
  public static function getInstance() {
    if (null === self::$_instance) return false;
    return self::$_instance;
  }

  /**
   * @param   string: account name 
   * @param   string: another account name 
   * @return boolean: true, if the contacts are contacts of each other
   */
  public function areContacts($contact1, $contact2) {
    // TODO: to be tested
    $this->request('friendships/exists.none?user_a='.$contact1.'&user_b='.$contact2);
    
    return false;
  }
  
  private function create_post_string($method, $params) {
    $params['method'] = $method;
    $post_params = array();
    foreach ($params as $key => &$val) {
      if (is_array($val)) $val = implode(',', $val);
      $post_params[] = $key.'='.urlencode($val);
    }
    return implode('&', $post_params);
  }

  public function request($method, $params=false) {
    if(false!==$params) $post_string = $this->create_post_string($method, $params);

    $url = $this->server_addr.(('/'==substr($this->server_addr, -1) and '/'==substr($method, 0, 1)) ? substr($method, 1) : $method); 
    
    if (function_exists('curl_init')) {
      // Use CURL if installed...
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      if(isset($post_string)) curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_VERBOSE, 0);
      curl_setopt($ch, CURLOPT_COOKIEJAR, "curl_cookies.txt");
      curl_setopt($ch, CURLOPT_COOKIEFILE, "curl_cookies.txt");
      curl_setopt($ch, CURLOPT_USERPWD, $this->username.":".$this->getPassword());
      
      curl_setopt($ch, CURLOPT_USERAGENT, 'No Mozilla although open source (curl) ' . phpversion());
      $result = curl_exec($ch);
      curl_close($ch);
    } else {
      // Non-CURL based version...
      /* @todo works only for read access of public accounts, 'cause cookie is not sent - feel free to fix it ;-) */
      $context =
        array('http' =>
              array('method' => isset($post_string) ? 'POST' : 'GET',
                    'header' => 'Content-type: application/x-www-form-urlencoded'."\r\n".
                                'User-Agent: No Mozilla although open source (non-curl) '.phpversion()."\r\n".
                                (isset($post_string) ? 'Content-length: ' . strlen($post_string) : ''),
                    'content' => isset($post_string) ? $post_string : null));
      $contextid=stream_context_create($context);

      $sock=fopen($url, 'r', false, $contextid);
      if ($sock) {
        $result='';
        while (!feof($sock))
          $result.=fgets($sock, 4096);
        fclose($sock);
      }
    }
    return $result;
  }
 
  private static function convert_xml_to_array($sxml) {
    $arr = array();
    if ($sxml) {
      foreach ($sxml as $k => $v) {
        if ($sxml['list']) {
          $arr[] = self::convert_xml_to_array($v);
        } else {
          $arr[$k] = self::convert_xml_to_array($v);
        }
      }
    }
    if (sizeof($arr) > 0) {
      return $arr;
    } else {
      return (string)$sxml;
    }
  }
  
  public function xmlObject2Array($knoten){
    $xmlArray = array();
    if(is_object($knoten)){
        settype($knoten,'array') ;
    }
    foreach ($knoten as $key=>$value){
        if(is_array($value)||is_object($value)){
            $xmlArray[$key] = $this->xmlObject2Array($value);
        }else{
            $xmlArray[$key] = $value;
        }
    }
    if(isset($xmlArray['error']) and 0 < count($xmlArray['error'])) {
      $this->connect('', $this->username);
    }
    
    return $xmlArray;
} 
  
  protected function mapContactData($contactData) {
    $contact = array(
      "accountName"         => $contactData["screen_name"],
      "uid"                 => $contactData["id"],
      "self_description"    => $contactData["description"],
      "name"                => $contactData["name"],
      "depiction"           => $contactData["profile_image_url"],
      "status"              => $contactData["status"]["text"],
      "timezone"            => $contactData["timezone"],
      "full_data"           => $contactData // for debugging: returns everything available
    );
    if(isset($contactData["url"]) and is_string($contactData["url"])) {
      $contact["homepage"] = $contactData["url"];
    }
    if(isset($contactData["location"]) and is_string($contactData["location"])) {
      $contact["based_near"] = $contactData["location"];
    }
    $contact['socializr_account_uri']=$this->getAccountIdentifier($contact);
    return $contact;
  }
  
  /**
   * setzt einen neuen Status
   * @param string Nachricht
   * @param mixed Empfänger
   * @param mixed ID der Nachricht, auf die geantwortet wird
   * @param string Betreff (wird bei Twitter ignoriert)
   */
  public function sendMessage($message, $receiver=null, $reply_message=null, $subject=null)
  {
    if(null != $receiver)
    {
      $message = '@'.$receiver.' '.$message;
    }
    $this->request('statuses/update.json', array('status' => $message));
  }

  /**
   * get the latest status messages of the current user
   * @param string $method 
   * @return array of all contacts including the contacts of the contacts of the regarded user
   */
  public function getMyLatestMessages($count=1)
  {
    $response = $this->request('statuses/user_timeline.json?count=' . $count);
    
    return json_decode($response);
  }
  
  /**
   * get the latest status messages of the specified user
   * @param string username
   * @param int number of messages to return
   */
  public function getLatestMessages($username, $count=1)
  {
    $response = $this->request('statuses/user_timeline.json?count=' . $count . '&id=' . $username);
    
    return json_decode($response);
  }
  
}
?>